// Copyright 2011 The Walk Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

const (
	tgppUrl  = "https://www.3gpp.org/ftp/Specs/archive/"
	giteeUrl = "https://gitee.com/konglinglong/TgppDownloader/Specs/archive/"
)

//TS REL技术标准释放版本
type TgppSeriesTsRelInfo struct {
	SeriesTsRelName string
	SeriesTsRelDate string
	SeriesTsRelSize string
}
type TgppSeriesTsRelInfoSort []TgppSeriesTsRelInfo

func (a TgppSeriesTsRelInfoSort) Len() int      { return len(a) }
func (a TgppSeriesTsRelInfoSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a TgppSeriesTsRelInfoSort) Less(i, j int) bool {
	return a[i].SeriesTsRelName < a[j].SeriesTsRelName
}

//TS技术标准
type TgppSeriesTsInfo struct {
	SeriesTsName    string
	SeriesTsSubject string
	SeriesTsRelList []TgppSeriesTsRelInfo
}
type TgppSeriesTsInfoSort []TgppSeriesTsInfo

func (a TgppSeriesTsInfoSort) Len() int           { return len(a) }
func (a TgppSeriesTsInfoSort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a TgppSeriesTsInfoSort) Less(i, j int) bool { return a[i].SeriesTsName < a[j].SeriesTsName }

//标准系列
type TgppSeriesInfo struct {
	SeriesName    string
	SeriesSubject string
	SeriesTsList  []TgppSeriesTsInfo
}
type TgppSeriesInfoSort []TgppSeriesInfo

func (a TgppSeriesInfoSort) Len() int           { return len(a) }
func (a TgppSeriesInfoSort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a TgppSeriesInfoSort) Less(i, j int) bool { return a[i].SeriesName < a[j].SeriesName }

type TgppSpecInfoTree struct {
	UpdateTime     string
	SeriesInfoList []TgppSeriesInfo
}

type TgppDownloader struct {
	httpConnNum  int32
	wg           sync.WaitGroup
	SpecIntoTree TgppSpecInfoTree
}

func NewDownloader() *TgppDownloader {
	return &TgppDownloader{}
}

func (loader *TgppDownloader) GetSeriesInfo() {

	atomic.StoreInt32(&loader.httpConnNum, 0)

	url := "https://www.3gpp.org/specifications/79-specification-numbering"
	fmt.Println(url)

	res, err := http.Get(url)
	if err != nil {
		log.Printf("GetSeriesInfo(%s) : %v", url, err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Printf("GetSeriesInfo(%s) : %s", url, res.Status)
		return
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		log.Printf("GetSeriesInfo(%s) : %v", url, err)
		return
	}

	//fmt.Println(doc.Text())

	var found = false

	var seriesInfo TgppSeriesInfo
	info := strings.Split(doc.Text(), "\n")
	for i := range info {
		tmp := strings.TrimSpace(info[i])
		if len(tmp) == 0 {
			continue
		}
		if strings.HasPrefix(tmp, "Subject of specification series") {
			found = true
			continue
		}
		if !found {
			continue
		}
		//fmt.Println(i, info[i])
		if strings.HasSuffix(tmp, "items.") {
			break
		}
		if strings.HasSuffix(tmp, "series") {
			seriesInfo.SeriesName = strings.TrimSpace(tmp)
			loader.SpecIntoTree.SeriesInfoList = append(loader.SpecIntoTree.SeriesInfoList, seriesInfo)
		} else {
			seriesInfo.SeriesSubject = strings.TrimSpace(tmp)
		}
	}

	sort.Sort(TgppSeriesInfoSort(loader.SpecIntoTree.SeriesInfoList))

	for i := range loader.SpecIntoTree.SeriesInfoList {
		//fmt.Println(e.Value.(*TgppSeriesInfo))
		loader.GetSeriesTsInfo(&loader.SpecIntoTree.SeriesInfoList[i])
	}
}

func (loader *TgppDownloader) GetSeriesTsInfo(seriesInfo *TgppSeriesInfo) {

	seriesNum := strings.Split(seriesInfo.SeriesName, " ")[0]
	url := "https://www.3gpp.org/DynaReport/" + seriesNum + "-series.htm"
	fmt.Println(url)

	res, err := http.Get(url)
	if err != nil {
		log.Printf("GetSeriesTsInfo(%s) : %v", url, err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Printf("GetSeriesTsInfo(%s) : %s", url, res.Status)
		return
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		log.Printf("GetSeriesTsInfo(%s) : %v", url, err)
		return
	}

	//fmt.Println(doc.Text())

	var seriesTsInfo TgppSeriesTsInfo
	doc.Find("tbody").Each(func(i int, s *goquery.Selection) {
		s.Find("tr[class] td[width=\"100\"] a,tr[class] td[width=\"500\"]").Each(func(j int, s1 *goquery.Selection) {
			if seriesTsInfo.SeriesTsName == "" {
				seriesTsInfo.SeriesTsName = strings.TrimSpace(s1.Text())
			} else {
				seriesTsInfo.SeriesTsSubject = strings.TrimSpace(s1.Text())
				seriesInfo.SeriesTsList = append(seriesInfo.SeriesTsList, seriesTsInfo)
				seriesTsInfo.SeriesTsName = ""
			}
		})
	})

	sort.Sort(TgppSeriesTsInfoSort(seriesInfo.SeriesTsList))

	for i := range seriesInfo.SeriesTsList {
		//fmt.Println(v)
		loader.wg.Add(1)
		atomic.AddInt32(&loader.httpConnNum, 1)
		go loader.GetSeriesTsRelInfo(&seriesInfo.SeriesTsList[i])
		for atomic.LoadInt32(&loader.httpConnNum) >= 32 {
			time.Sleep(100 * time.Millisecond)
		}
	}
}

func (loader *TgppDownloader) GetSeriesTsRelInfo(seriesTsInfo *TgppSeriesTsInfo) {

	defer loader.wg.Done()
	defer atomic.AddInt32(&loader.httpConnNum, -1)

	tsNum := strings.Split(seriesTsInfo.SeriesTsName, ".")[0]
	url := "https://www.3gpp.org/ftp/Specs/archive/" + tsNum + "_series/" + seriesTsInfo.SeriesTsName
	fmt.Println(url)

	res, err := http.Get(url)
	if err != nil {
		log.Printf("GetSeriesTsRelInfo(%s) : %v", url, err)
		return
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Printf("GetSeriesTsRelInfo(%s) : %s", url, res.Status)
		return
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		log.Printf("GetSeriesTsRelInfo(%s) : %v", url, err)
		return
	}

	//fmt.Println(doc.Text())

	var seriesTsRelInfo TgppSeriesTsRelInfo
	doc.Find("tbody").Each(func(i int, s *goquery.Selection) {
		s.Find("td[style] a[href],td[style]+td[style],td[style]+td[style]+td").Each(func(j int, s1 *goquery.Selection) {
			if seriesTsRelInfo.SeriesTsRelName == "" {
				seriesTsRelInfo.SeriesTsRelName = strings.Split(strings.TrimSpace(s1.Text()), ".")[0]
			} else if seriesTsRelInfo.SeriesTsRelDate == "" {
				seriesTsRelInfo.SeriesTsRelDate = strings.TrimSpace(s1.Text())
			} else {
				seriesTsRelInfo.SeriesTsRelSize = strings.TrimSpace(s1.Text())
				seriesTsInfo.SeriesTsRelList = append(seriesTsInfo.SeriesTsRelList, seriesTsRelInfo)

				seriesTsRelInfo.SeriesTsRelName = ""
				seriesTsRelInfo.SeriesTsRelDate = ""
				seriesTsRelInfo.SeriesTsRelSize = ""
			}
		})
	})

	sort.Sort(TgppSeriesTsRelInfoSort(seriesTsInfo.SeriesTsRelList))

	for _, v := range seriesTsInfo.SeriesTsRelList {
		fmt.Println(&v)
	}
}

func (loader *TgppDownloader) WaitRoutines() {
	loader.wg.Wait()
}

const (
	MyTreeItemTypeRoot = iota
	MyTreeItemTypeSeries
	MyTreeItemTypeSeriesTs
	MyTreeItemTypeSeriesTsRel
)

type MyTreeItem struct {
	Txt             string
	Type            int
	SeriesName      string
	SeriesTsName    string
	SeriesTsRelName string
	Subject         string
	Date            string
	Size            string
	tgppUrl         string
	giteeUrl        string
	parent          *MyTreeItem
	children        []*MyTreeItem
}
type MyTreeItemSort []*MyTreeItem

func (a MyTreeItemSort) Len() int           { return len(a) }
func (a MyTreeItemSort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a MyTreeItemSort) Less(i, j int) bool { return a[i].Txt < a[j].Txt }

func NewMyTreeItem(txt string, parent *MyTreeItem) *MyTreeItem {
	return &MyTreeItem{Txt: txt, parent: parent}
}

var _ walk.TreeItem = new(MyTreeItem)

func (d *MyTreeItem) Text() string {
	return d.Txt
}

func (d *MyTreeItem) Parent() walk.TreeItem {
	if d.parent == nil {
		return nil
	}
	return d.parent
}

func (d *MyTreeItem) ChildCount() int {
	if d.children == nil {
		return 0
	}
	return len(d.children)
}

func (d *MyTreeItem) ChildAt(index int) walk.TreeItem {
	if d.children == nil {
		return nil
	}
	return d.children[index]
}

func (d *MyTreeItem) GetChild(name string) *MyTreeItem {
	if d.children == nil {
		return nil
	}
	for _, v := range d.children {
		if v.Text() == name {
			return v
		}
	}
	return nil
}

type MyTreeModel struct {
	walk.TreeModelBase
	roots []*MyTreeItem
}

var _ walk.TreeModel = new(MyTreeModel)

func NewMyTreeModel() *MyTreeModel {
	model := new(MyTreeModel)
	model.roots = append(model.roots, NewMyTreeItem("3GPP标准系列", nil))
	model.roots[0].Type = MyTreeItemTypeRoot

	data, err3 := ioutil.ReadFile("3gppSpecTree1.json")
	if err3 != nil {
		return nil
	}
	var specInfo TgppSpecInfoTree
	err4 := json.Unmarshal(data, &specInfo)
	if err4 != nil {
		log.Fatalln(err4)
	}
	//log.Println(specInfo)

	for _, v := range specInfo.SeriesInfoList {
		gDataChs <- v
	}

	//for _, v := range specInfo.SeriesInfoList {
	//	model.roots[0].children = append(model.roots[0].children, NewMyTreeItem(v.SeriesName, model.roots[0]))
	//}

	return model
}

func (*MyTreeModel) LazyPopulation() bool {
	// We don't want to eagerly populate our tree view with the whole file system.
	return true
}

func (m *MyTreeModel) RootCount() int {
	return len(m.roots)
}

func (m *MyTreeModel) RootAt(index int) walk.TreeItem {
	return m.roots[index]
}

type MyTableItem struct {
	Name        string
	Type        int
	Description string
	Size        string
	Modified    string
	tgppUrl     string
	giteeUrl    string
}

type MyTableModel struct {
	walk.SortedReflectTableModelBase
	items []*MyTableItem
}

var _ walk.ReflectTableModel = new(MyTableModel)

func NewMyTableModel() *MyTableModel {
	return new(MyTableModel)
}

func (m *MyTableModel) Items() interface{} {
	return m.items
}

func updateTableColumnsView(mw *MyMainWindow) {

	treeItem := mw.treeView.CurrentItem()
	tableColumns := mw.tableView.Columns()

	if treeItem == nil || tableColumns == nil {
		return
	}

	for i := 0; i < tableColumns.Len(); i++ {
		tableColumns.At(i).SetVisible(false)
	}

	if treeItem.(*MyTreeItem).Type == MyTreeItemTypeRoot {
		width := mw.tableView.Size().Width / 4
		tableColumns.At(0).SetVisible(true)
		tableColumns.At(0).SetWidth(width)
		tableColumns.At(1).SetVisible(true)
		tableColumns.At(1).SetWidth(mw.tableView.Size().Width - width)
	} else if treeItem.(*MyTreeItem).Type == MyTreeItemTypeSeries {
		width := mw.tableView.Size().Width / 4
		tableColumns.At(0).SetVisible(true)
		tableColumns.At(0).SetWidth(width)
		tableColumns.At(1).SetVisible(true)
		tableColumns.At(1).SetWidth(mw.tableView.Size().Width - width)

	} else if treeItem.(*MyTreeItem).Type == MyTreeItemTypeSeriesTs {
		width := mw.tableView.Size().Width / 3
		tableColumns.At(0).SetVisible(true)
		tableColumns.At(0).SetWidth(width)
		tableColumns.At(2).SetVisible(true)
		tableColumns.At(2).SetWidth(width)
		tableColumns.At(3).SetVisible(true)
		tableColumns.At(3).SetWidth(width)
	} else {
		width := mw.tableView.Size().Width / 3
		tableColumns.At(0).SetVisible(true)
		tableColumns.At(0).SetWidth(width)
		tableColumns.At(2).SetVisible(true)
		tableColumns.At(2).SetWidth(width)
		tableColumns.At(3).SetVisible(true)
		tableColumns.At(3).SetWidth(width)
	}
}

type MyMainWindow struct {
	*walk.MainWindow
	treeModel    *MyTreeModel
	tableModel   *MyTableModel
	splitter     *walk.Splitter
	treeView     *walk.TreeView
	tableView    *walk.TableView
	specInfoTree TgppSpecInfoTree
}

var gDataChs chan TgppSeriesInfo

func main() {
	//start := time.Now()
	//
	//downLoader := NewDownloader()
	//downLoader.GetSeriesInfo()
	//downLoader.WaitRoutines()
	//
	////耗时打印
	//fmt.Printf("elapsed time : %s\n", time.Since(start))
	//
	//rs, err := json.Marshal(downLoader.SpecIntoTree)
	//if err != nil{
	//	log.Fatalln(err)
	//}
	//
	////fmt.Println(string(rs))
	//
	//file, err1 := os.OpenFile("3gppSpecTree.json", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0766)
	//if err1 != nil {
	//	return
	//}
	//file.Write(rs)
	//file.Close()
	//
	//return

	gDataChs = make(chan TgppSeriesInfo, 256)

	mw := &MyMainWindow{treeModel: NewMyTreeModel(), tableModel: NewMyTableModel()}

	if err := (MainWindow{
		AssignTo: &mw.MainWindow,
		Title:    "3GPP Downloader",
		MinSize:  Size{800, 600},
		Size:     Size{1024, 640},
		//Font:     Font{PointSize: 10},
		Layout: HBox{MarginsZero: false},
		Children: []Widget{
			HSplitter{
				AssignTo: &mw.splitter,
				Children: []Widget{
					TreeView{
						AssignTo: &mw.treeView,
						Model:    mw.treeModel,
						OnCurrentItemChanged: func() {
							treeItem := mw.treeView.CurrentItem().(*MyTreeItem)
							mw.tableModel.items = nil

							for _, v := range treeItem.children {
								item := &MyTableItem{
									Name:        v.Txt,
									Description: v.Subject,
									Size:        v.Size,
									Modified:    v.Date,
									tgppUrl:     v.tgppUrl,
									giteeUrl:    v.giteeUrl,
									Type:        v.Type,
								}
								mw.tableModel.items = append(mw.tableModel.items, item)
							}

							updateTableColumnsView(mw)

							mw.tableModel.PublishRowsReset()
						},
						//OnItemActivated: func() {
						//	log.Println("OnItemActivated")
						//	//mw.treeView.SetExpanded(mw.treeModel.roots[0], false)
						//},
					},
					TableView{
						AssignTo:      &mw.tableView,
						StretchFactor: 4,
						Columns: []TableViewColumn{
							TableViewColumn{
								DataMember: "Name",
								Alignment:  AlignNear,
								//Width:      tableView.Size().Width / 3,
							},
							TableViewColumn{
								DataMember: "Description",
								Alignment:  AlignNear,
								//Width:      tableView.Size().Width / 3,
							},
							TableViewColumn{
								DataMember: "Size",
								Format:     "%d",
								Alignment:  AlignNear,
								//Width:      tableView.Size().Width / 3,
							},
							TableViewColumn{
								DataMember: "Modified",
								Format:     "2006-01-02 15:04:05",
								Alignment:  AlignNear,
								//Width:      tableView.Size().Width / 3,
							},
						},
						Model: mw.tableModel,
						OnCurrentIndexChanged: func() {
						},
						OnItemActivated: func() {
							log.Println("1 OnItemActivated")
							currIdx := mw.tableView.CurrentIndex()
							if currIdx != -1 && mw.tableModel.items[currIdx].Type == MyTreeItemTypeSeriesTsRel {
								fmt.Println(mw.tableModel.items[currIdx].tgppUrl)
							}
						},
						OnSizeChanged: func() {
							log.Println("1 OnSizeChanged")
							updateTableColumnsView(mw)
						},
					},
					//WebView{
					//	AssignTo:      &webView,
					//	StretchFactor: 2,
					//},
				},
			},
		},
	}.Create()); err != nil {
		log.Fatal(err)
	}

	mw.splitter.SetFixed(mw.treeView, true)
	mw.splitter.SetFixed(mw.tableView, true)

	go mw.SpecInfoUpdateWorker()

	mw.MainWindow.Run()
}

func (mw *MyMainWindow) SpecInfoUpdateWorker() {
	for {
		data, ok := <-gDataChs
		if !ok {
			break
		}

		//log.Println(data)

		root := mw.treeModel.roots[0]

		seriesItem := root.GetChild(data.SeriesName)
		if seriesItem == nil {
			seriesItem = NewMyTreeItem(data.SeriesName, root)
			seriesItem.Type = MyTreeItemTypeSeries
			seriesItem.Subject = data.SeriesSubject
			root.children = append(root.children, seriesItem)
			sort.Sort(MyTreeItemSort(root.children))
		}

		for _, value1 := range data.SeriesTsList {
			seriesTsItem := seriesItem.GetChild(value1.SeriesTsName)
			if seriesTsItem == nil {
				seriesTsItem = NewMyTreeItem(value1.SeriesTsName, seriesItem)
				seriesTsItem.Type = MyTreeItemTypeSeriesTs
				seriesTsItem.Subject = value1.SeriesTsSubject
				seriesItem.children = append(seriesItem.children, seriesTsItem)
				sort.Sort(MyTreeItemSort(seriesItem.children))
			}

			for _, value2 := range value1.SeriesTsRelList {
				seriesTsRelItem := seriesTsItem.GetChild(value2.SeriesTsRelName)
				if seriesTsRelItem == nil {
					seriesTsRelItem = NewMyTreeItem(value2.SeriesTsRelName, seriesTsItem)
					seriesTsRelItem.Type = MyTreeItemTypeSeriesTsRel
					seriesTsRelItem.SeriesName = data.SeriesName
					seriesTsRelItem.SeriesTsName = value1.SeriesTsName
					seriesTsRelItem.SeriesTsRelName = value2.SeriesTsRelName
					seriesTsRelItem.Date = value2.SeriesTsRelDate
					seriesTsRelItem.Size = value2.SeriesTsRelSize
					seriesTsRelItem.tgppUrl = tgppUrl + strings.ReplaceAll(seriesTsRelItem.SeriesName, " ", "_") + "/" + seriesTsRelItem.SeriesTsName + "/" + seriesTsRelItem.SeriesTsRelName + ".zip"
					seriesTsRelItem.giteeUrl = giteeUrl + strings.ReplaceAll(seriesTsRelItem.SeriesName, " ", "_") + "/" + seriesTsRelItem.SeriesTsName + "/" + seriesTsRelItem.SeriesTsRelName + ".zip"
					//fmt.Println(seriesTsRelItem.tgppUrl)

					seriesTsItem.children = append(seriesTsItem.children, seriesTsRelItem)
					sort.Sort(MyTreeItemSort(seriesTsItem.children))
				}
			}
		}
	}
}
